package com.todo.sight.application.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.todo.common.common.entity.response.page.PageResult;
import com.todo.common.common.enums.BizErrorCodeEnum;
import com.todo.common.common.exception.BizException;
import com.todo.common.utils.StringUtils;
import com.todo.component.redis.RedisService;
import com.todo.sight.application.convert.SightServiceConvert;
import com.todo.sight.application.entity.bo.*;
import com.todo.sight.application.entity.condition.SightQueryConditionBO;
import com.todo.sight.application.service.CityInfoService;
import com.todo.sight.application.service.SightPoiService;
import com.todo.sight.application.service.SightService;
import com.todo.sight.common.base.SightKeyBuild;
import com.todo.sight.infrastructure.entity.condition.SightBaseCondition;
import com.todo.sight.infrastructure.entity.domain.SightInfoDO;
import com.todo.sight.infrastructure.entity.po.SightBasePO;
import com.todo.sight.infrastructure.helper.es.entity.SightDetailEntity;
import com.todo.sight.infrastructure.helper.es.entity.SightPoiEntity;
import com.todo.sight.infrastructure.helper.es.mapper.SightDetailEntityMapper;
import com.todo.sight.infrastructure.helper.es.mapper.SightPoiEntityMapper;
import com.todo.sight.infrastructure.repository.SightInfoRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.dromara.easyes.core.biz.EsPageInfo;
import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * (SightBase)表服务实现类
 *
 * @author zhangwenxiang
 * @since 2024-03-04 00:19:48
 */
@Slf4j
@Service("sightService")
public class SightServiceImpl implements SightService {


    public static final int MAX_QUERY_SIZE = 100;

    @Resource
    private SightInfoRepository sightInfoRepository;
    @Resource
    private SightPoiService sightPoiService;
    @Resource
    private CityInfoService cityInfoService;
    @Resource
    private RedisService redisService;
    @Resource
    private SightDetailEntityMapper sightDetailEntityMapper;
    @Resource
    private SightPoiEntityMapper sightPoiEntityMapper;

    @Override
    public Boolean insertSightDetail(SightDetailBO sightDetailBO) {
        if (sightDetailBO == null) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR);
        }
        CityInfoBO cityInfoBO = cityInfoService.queryCityInfoByName(sightDetailBO.getCity());
        if (cityInfoBO == null) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR, "城市不存在");
        }
        SightPoiBO sightPoiBO = sightPoiService.querySightPoiById(sightDetailBO.getPoiId());
        if (sightPoiBO == null) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR, "POI信息不存在");
        }
        if (!checkPoiAndSightAddress(sightPoiBO, sightDetailBO)) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR, "POI信息与景点基础信息未知不匹配");
        }
        SightInfoDO sightInfoDO = SightServiceConvert.convertToSightInfoDO(sightDetailBO);
        Boolean inserted = sightInfoRepository.insertSightInfoDetail(sightInfoDO);
        if (inserted) {
            String key = SightKeyBuild.sightDetailKey(sightInfoDO.getId());
            redisService.set8H(key, JSONObject.toJSONString(sightInfoDO));
            String relationKey = SightKeyBuild.sightDetailPoiRelationKey(sightDetailBO.getPoiId());
            redisService.set8H(relationKey, String.valueOf(sightDetailBO.getId()));
            SightDetailEntity sightDetailEntity = SightServiceConvert.convertToSightDetailEntity(sightInfoDO);
            sightDetailEntityMapper.insert(sightDetailEntity);
        }
        return inserted;
    }

    @Override
    public Boolean updateSightDetail(SightDetailBO sightDetailBO) {
        if (sightDetailBO == null || sightDetailBO.getId() == null) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR);
        }
        SightBaseBO sightBaseBO = querySightBaseById(sightDetailBO.getId());
        if (sightBaseBO == null) {
            log.warn("SightService#updateSightDetail 更新景点信息失败，根据主键Id查询数据为空 id：【{}】", sightDetailBO.getId());
            return false;
        }
        //如果POI信息变更
        if (sightBaseBO.getPoiId().equals(sightDetailBO.getPoiId())) {
            SightPoiBO sightPoiBO = sightPoiService.querySightPoiById(sightDetailBO.getPoiId());
            if (sightPoiBO == null) {
                throw new BizException(BizErrorCodeEnum.PARAM_ERROR, "POI信息不存在");
            }
            if (!checkPoiAndSightAddress(sightPoiBO, sightDetailBO)) {
                throw new BizException(BizErrorCodeEnum.PARAM_ERROR, "POI信息与景点基础信息未知不匹配");
            }
        }
        SightInfoDO sightInfoDO = SightServiceConvert.convertToSightInfoDO(sightDetailBO);
        Boolean updated = sightInfoRepository.updateSightInfoDetailById(sightInfoDO);
        if (updated) {
            String key = SightKeyBuild.sightDetailKey(sightDetailBO.getId());
            redisService.del(key);
            SightDetailEntity sightDetailEntity = SightServiceConvert.convertToSightDetailEntity(sightInfoDO);
            LambdaEsQueryWrapper<SightDetailEntity> wrapper = new LambdaEsQueryWrapper<>();
            wrapper.eq(SightDetailEntity::getMysqlId, sightInfoDO.getId());
            sightDetailEntityMapper.update(sightDetailEntity, wrapper);
        }
        return updated;
    }

    @Override
    public Boolean removeSightById(Long id) {
        if (id == null || id < 0) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR);
        }
        SightBaseBO sightBaseBO = querySightBaseById(id);
        if (sightBaseBO == null) {
            return true;
        }
        Boolean removed = sightInfoRepository.removeSightBasePOById(id);
        if (removed) {
            String key = SightKeyBuild.sightDetailKey(id);
            redisService.del(key);
            String relationKey = SightKeyBuild.sightDetailPoiRelationKey(sightBaseBO.getPoiId());
            redisService.del(relationKey);
            LambdaEsQueryWrapper<SightDetailEntity> wrapper = new LambdaEsQueryWrapper<>();
            wrapper.eq(SightDetailEntity::getMysqlId, id);
            sightDetailEntityMapper.delete(wrapper);
        }
        return removed;
    }

    @Override
    public Boolean showSightById(Long id, Integer show) {
        if (id == null || id < 0) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR);
        }
        SightBaseBO sightBaseBO = querySightBaseById(id);
        if (sightBaseBO == null) {
            throw new BizException(BizErrorCodeEnum.DATA_NOT_EXIST);
        }
        if (sightBaseBO.getIsShow().equals(show)) {
            return true;
        }
        return sightInfoRepository.showSightBasePOById(id, show);
    }

    @Override
    public SightDetailBO querySightDetailById(Long id) {
        if (id == null || id <= 0) {
            return null;
        }
        String key = SightKeyBuild.sightDetailKey(id);
        SightDetailBO sightDetailBO = JSONObject.parseObject(redisService.get(key), SightDetailBO.class);
        if (sightDetailBO != null) {
            return sightDetailBO;
        }
        SightInfoDO sightInfoDO = sightInfoRepository.querySightInfoDetailById(id);
        if (sightInfoDO == null) {
            return null;
        }
        SightDetailBO alreadyData = SightServiceConvert.convertToSightDetailBO(sightInfoDO);
        redisService.set8H(key, JSONObject.toJSONString(alreadyData));
        String relationKey = SightKeyBuild.sightDetailPoiRelationKey(sightInfoDO.getSightBase().getPoiId());
        redisService.set8H(relationKey, String.valueOf(sightInfoDO.getId()));
        return alreadyData;
    }

    @Override
    public SightDetailBO querySightDetailByPoi(Long poiId) {
        if (poiId == null || poiId < 0) {
            return null;
        }
        String relationKey = SightKeyBuild.sightDetailPoiRelationKey(poiId);
        String cacheValue = redisService.get(relationKey);
        if (StringUtils.isNotEmpty(cacheValue)) {
            Long sightId = Long.valueOf(cacheValue);
            SightDetailBO sightDetailBO = querySightDetailById(sightId);
            if (sightDetailBO != null) {
                return sightDetailBO;
            }
        }
        SightInfoDO sightInfoDO = sightInfoRepository.querySightInfoDetailByPOI(poiId);
        if (sightInfoDO == null || sightInfoDO.getSightBase() == null || sightInfoDO.getSightDetail() == null) {
            return null;
        }
        SightDetailBO result = SightServiceConvert.convertToSightDetailBO(sightInfoDO);
        redisService.set(relationKey, String.valueOf(sightInfoDO.getId()));
        String detailKey = SightKeyBuild.sightDetailKey(sightInfoDO.getId());
        redisService.set(detailKey, JSONObject.toJSONString(result));
        return result;
    }

    @Override
    public SightBaseBO querySightBaseById(Long id) {
        if (id == null || id < 0) {
            return null;
        }
        SightDetailBO sightDetailBO = querySightDetailById(id);
        if (sightDetailBO == null) {
            return null;
        }
        return SightServiceConvert.convertToSightBaseBO(sightDetailBO);
    }

    @Override
    public List<SightBaseBO> querySightBaseById(List<Long> sightIdList) {
        if (CollectionUtils.isEmpty(sightIdList)) {
            return Collections.emptyList();
        }
        if (sightIdList.size() > MAX_QUERY_SIZE) {
            return Collections.emptyList();
        }
        return sightIdList.stream().map(this::querySightBaseById).collect(Collectors.toList());
    }

    @Override
    public PageResult<SightBaseBO> querySightBaseListByCondition(SightQueryConditionBO sightQueryConditionBO) {
        if (sightQueryConditionBO == null) {
            return new PageResult<>(Lists.newArrayList(), 0, 0, 0, 0);
        }
        SightBaseCondition sightBaseCondition = SightServiceConvert.convertToSightBaseCondition(sightQueryConditionBO);
        PageResult<SightBasePO> sightBasePOPageResult = sightInfoRepository.querySightBaseByCondition(sightBaseCondition);
        if (sightBasePOPageResult == null || CollectionUtils.isEmpty(sightBasePOPageResult.getResults())) {
            return new PageResult<>(Lists.newArrayList(), sightQueryConditionBO.getPageIndex(), sightQueryConditionBO.getPageSize(), 0, 0);
        }
        PageResult<SightBaseBO> result = new PageResult<>();
        result.setResults(SightServiceConvert.convertToSightBaseBOList(sightBasePOPageResult.getResults()));
        result.setCurrentIndex(sightBasePOPageResult.getCurrentIndex());
        result.setPageCount(sightBasePOPageResult.getPageCount());
        result.setPageSize(sightBasePOPageResult.getPageSize());
        result.setTotal(sightBasePOPageResult.getTotal());
        return result;
    }

    @Override
    public List<SightBaseBO> querySightInfoByPoiIdList(List<Long> poiIdList) {
        if (CollectionUtils.isEmpty(poiIdList)) {
            return Collections.emptyList();
        }
        if (poiIdList.size() > MAX_QUERY_SIZE) {
            log.warn("SightService#querySightInfoByIdList 查询数量较大，超过查询限制，无法查询 idList：【{}】", JSONObject.toJSONString(poiIdList));
            throw new BizException(BizErrorCodeEnum.QUANTITY_EXPLOSION);
        }
        //todo V1版本先嵌套查询，后续优化
        List<SightBaseBO> resultList = new ArrayList<>();
        for (Long poiId : poiIdList) {
            String relationKey = SightKeyBuild.sightDetailPoiRelationKey(poiId);
            String cacheValue = redisService.get(relationKey);
            if (StringUtils.isEmpty(cacheValue)){
                continue;
            }
            Long sightId = Long.valueOf(cacheValue);
            SightBaseBO sightBaseBO = querySightBaseById(sightId);
            if (sightBaseBO != null) {
                resultList.add(sightBaseBO);
            }
            SightDetailBO sightDetailBO = querySightDetailByPoi(poiId);
            if (sightDetailBO != null) {
                resultList.add(SightServiceConvert.convertToSightBaseBO(sightDetailBO));
            }
        }
        return resultList;
    }

    @Override
    public PageResult<SearchSightPoiDetailBO> searchSightPoi(SearchSightPoiBO searchSightPoiBO) {
        if (searchSightPoiBO == null) {
            return PageResult.of(Collections.emptyList(), 0, 0, 0);
        }
        LambdaEsQueryWrapper<SightPoiEntity> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.match(SightPoiEntity::getName, searchSightPoiBO.getKeyword());
        if (searchSightPoiBO.getCity() != null) {
            wrapper.eq(SightPoiEntity::getCity, searchSightPoiBO.getCity());
        }
        if (searchSightPoiBO.getProvince() != null) {
            wrapper.eq(SightPoiEntity::getProvince, searchSightPoiBO.getProvince());
        }
        EsPageInfo<SightPoiEntity> pageQuery = sightPoiEntityMapper.pageQuery(wrapper, searchSightPoiBO.getPageIndex(), searchSightPoiBO.getPageSize());
        if (pageQuery == null || CollectionUtils.isEmpty(pageQuery.getList())) {
            log.warn("SightService#searchSightPoi 进行数据搜索完成，数据为空 入参：{}", JSONObject.toJSONString(searchSightPoiBO));
            return PageResult.of(Collections.emptyList(), searchSightPoiBO.getPageIndex(), searchSightPoiBO.getPageSize(), pageQuery.getTotal());
        }
        PageResult<SearchSightPoiDetailBO> result = new PageResult<>();
        List<SearchSightPoiDetailBO> resultList = Lists.newArrayList();
        for (SightPoiEntity entity : pageQuery.getList()) {
            SearchSightPoiDetailBO detailBO = SightServiceConvert.convertToSearchSightDetailBO(entity);
            //todo 此处缓存可能不存在，后续调整存储介质即可，例如hbase，或者做永久保存，方案再议
            if (detailBO != null) {
                Boolean has = redisService.hasKey(SightKeyBuild.sightDetailKey(detailBO.getPoiId()));
                detailBO.setHasSightDetail(has);
                if (has) {
                    String sightId = redisService.get(SightKeyBuild.sightDetailPoiRelationKey(detailBO.getPoiId()));
                    detailBO.setSightId(Long.valueOf(sightId));
                }
                resultList.add(detailBO);
            }
        }
        result.setResults(resultList);
        result.setTotal(pageQuery.getTotal());
        result.setHasNext(pageQuery.isHasNextPage());
        result.setPageNum(searchSightPoiBO.getPageIndex());
        result.setPageSize(searchSightPoiBO.getPageSize());
        List<Long> searchIdList = resultList.stream().map(SearchSightPoiDetailBO::getPoiId).collect(Collectors.toList());
        log.info("SightService#searchSightPoi 进行数据搜索完成，入参：{},结果IdList：{}", JSONObject.toJSONString(searchSightPoiBO), JSONObject.toJSONString(searchIdList));
        return result;
    }

    @Override
    public PageResult<SearchSightSummaryDetailBO> searchSightSummary(SearchSightSummaryBO searchSightSummaryBO) {
        if (searchSightSummaryBO == null) {
            return PageResult.of(Collections.emptyList(), 0, 0, 0);
        }
        LambdaEsQueryWrapper<SightDetailEntity> wrapper = new LambdaEsQueryWrapper<>();
        wrapper.multiMatchQuery(searchSightSummaryBO.getKeyword(), SightDetailEntity::getTitle, SightDetailEntity::getDesc);
        if (searchSightSummaryBO.getCity() != null) {
            wrapper.eq(SightDetailEntity::getCity, searchSightSummaryBO.getCity());
        }
        if (searchSightSummaryBO.getProvince() != null) {
            wrapper.eq(SightDetailEntity::getProvince, searchSightSummaryBO.getProvince());
        }
        EsPageInfo<SightDetailEntity> pageQuery = sightDetailEntityMapper.pageQuery(wrapper, searchSightSummaryBO.getPageIndex(), searchSightSummaryBO.getPageSize());
        if (pageQuery == null || CollectionUtils.isEmpty(pageQuery.getList())) {
            log.warn("SightService#searchSightSummary 进行数据搜索完成，数据为空 入参：{}", JSONObject.toJSONString(searchSightSummaryBO));
            return PageResult.of(Collections.emptyList(), searchSightSummaryBO.getPageIndex(), searchSightSummaryBO.getPageSize(), pageQuery.getTotal());
        }
        PageResult<SearchSightSummaryDetailBO> result = new PageResult<>();
        List<SearchSightSummaryDetailBO> resultList = Lists.newArrayList();
        for (SightDetailEntity entity : pageQuery.getList()) {
            SightPoiBO sightPoiBO = sightPoiService.querySightPoiById(entity.getPoiId());
            SearchSightSummaryDetailBO detailBO = SightServiceConvert.buildSearchSightSummaryDetailBO(entity, sightPoiBO);
            if (detailBO != null) {
                resultList.add(detailBO);
            }
        }
        result.setResults(resultList);
        result.setTotal(pageQuery.getTotal());
        result.setHasNext(pageQuery.isHasNextPage());
        result.setPageNum(searchSightSummaryBO.getPageIndex());
        result.setPageSize(searchSightSummaryBO.getPageSize());
        List<Long> searchIdList = resultList.stream().map(SearchSightSummaryDetailBO::getSightId).collect(Collectors.toList());
        log.info("SightService#searchSightSummary 进行数据搜索完成，入参：{},结果IdList：{}", JSONObject.toJSONString(searchSightSummaryBO), JSONObject.toJSONString(searchIdList));
        return result;
    }


    /**
     * 校验poi和景点详情基本地理位置是否一致
     *
     * @param sightPoiBO
     * @param sightDetailBO
     * @return
     */
    private boolean checkPoiAndSightAddress(SightPoiBO sightPoiBO, SightDetailBO sightDetailBO) {
        if (!sightPoiBO.getProvince().equals(sightDetailBO.getProvince())) {
            return false;
        }
        if (!sightPoiBO.getCity().equals(sightDetailBO.getCity())) {
            return false;
        }
        //暂时不做具体信息未知校验
        return true;
    }
}
